package com.lizbet.server;

import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.apache.xml.serialize.OutputFormat;
import org.apache.xml.serialize.XMLSerializer;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

import com.lbs.core.shared.XmlRawInterface;



/**
 *  Funciones basicas de un documentos XML en el servidor
 */
public class XmlRaw implements XmlRawInterface{

	  protected Document xmlDoc = null;
	  protected Element doc = null;
	  protected String domainPrefix = "";
	  
	  XmlRaw(){
		  
	  }
	  
	  /**
	   * Crea un objeto xmlRaw a partir de un byteArray
	   */
	    public XmlRaw(byte[] data) throws myXmlParsingException{
	        //get the factory
	        DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
	        //Using factory get an instance of document builder
	        DocumentBuilder db;
	        ByteArrayInputStream ins = new ByteArrayInputStream(data);
			try {
				db = dbf.newDocumentBuilder();
				xmlDoc = db.parse(ins);
				doc = xmlDoc.getDocumentElement();
				if (doc.getAttribute("version") == null || doc.getAttribute("version").isEmpty())
  					domainPrefix = "cfdi:";
  				else if (doc.getAttribute("version").startsWith("3"))
			    	domainPrefix = "cfdi:";
			} catch (SAXException e) {
				throw new myXmlParsingException();
			} catch (IOException e) {
				throw new myXmlParsingException();
			} catch (ParserConfigurationException e) {
				throw new myXmlParsingException();
			}
	    }
	    
		  /**
		   * Crea un objeto xmlRaw a partir de un Nodo
		   */
		    public XmlRaw(Element element) throws myXmlParsingException{
		        //get the factory
		        DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
		        //Using factory get an instance of document builder
		        DocumentBuilder db;
				try {
					db = dbf.newDocumentBuilder();
					xmlDoc = db.newDocument();
					xmlDoc.adoptNode(element);
					xmlDoc.appendChild(element);
					doc=element;
					if (doc.getAttribute("version") == null || doc.getAttribute("version").isEmpty())
	  					domainPrefix = "cfdi:";
	  				else if (doc.getAttribute("version").startsWith("3"))
				    	domainPrefix = "cfdi:";
				} catch (ParserConfigurationException e) {
					throw new myXmlParsingException();
				}
		    }
	    
	    /**
	     * Crea un objeto xmlFact a partir de un archivo XML
	     */
	    public  XmlRaw(String path) throws myXmlParsingException{

	              //get the factory
	              DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
	              //Using factory get an instance of document builder
	              DocumentBuilder db;
	  			try {
	  				db = dbf.newDocumentBuilder();
	  				xmlDoc = db.parse(path);
	  				doc = xmlDoc.getDocumentElement();
	  				if (doc.getAttribute("version") == null || doc.getAttribute("version").isEmpty())
	  					domainPrefix = "cfdi:";
	  				else if (doc.getAttribute("version").startsWith("3"))
				    	domainPrefix = "cfdi:";
	  			} catch (ParserConfigurationException e1) {
	  				e1.printStackTrace();
	  			} catch (SAXException e) {
	  				e.printStackTrace();
	  			} catch (IOException e) {
	  				e.printStackTrace();
	  			}
	      }
    
	     
	     /**
	      * Contructor para inicializar un XmlRaw a partir de un InputStream
	      * @param is
	      * @throws ParserConfigurationException
	      * @throws SAXException
	      * @throws IOException
	      */
	     public XmlRaw(InputStream is) throws ParserConfigurationException, SAXException, IOException{
	             //get the factory
	             DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
	             //Using factory get an instance of document builder
	             DocumentBuilder db = dbf.newDocumentBuilder();
	             //parse using builder to get DOM representation of the XML file
	             xmlDoc = db.parse(is);
	             doc = xmlDoc.getDocumentElement();
	     }

	     
	     
		/**
		 * Devuelve el documentos XML como una cadena de texto
		 */
	      @SuppressWarnings("deprecation")
		private String getAsText() {
	          StringWriter strWriter = null;
	          XMLSerializer xmlSerializer = null;
	          OutputFormat outFormat = null;
	          try {
	            xmlSerializer = new XMLSerializer();
	            strWriter = new StringWriter();
	            outFormat = new OutputFormat();

	            outFormat.setEncoding("UTF-8");
	            //outFormat.setVersion();
	            outFormat.setIndenting(true);
	            outFormat.setIndent(4);

	            xmlSerializer.setOutputCharStream(strWriter);
	            xmlSerializer.setOutputFormat(outFormat);
	            xmlSerializer.serialize(xmlDoc);
	            strWriter.close();
	          } catch (IOException ioEx) {
	            System.out.println("Error : " + ioEx);
	          }
	          return strWriter.toString();

	        }


	      /**
	       * Devuelve el atributo attrib del elemento indicado por path
	       * 
	       * @Param path: Path al elemento sin contar la raiz, para referirse a la raiz utilizar null
	       * @Param attrib: Nombre del atributo
	       */
	      public String getAttrib(String path, String attrib){
	    	    Element element = xmlDoc.getDocumentElement();
	    	    if (path!=null){
	    	    	String[] paths = path.split("/");
	    	    	for (int x=0;x<paths.length;x++)
		  	    		if (element==null)
		  	    			break;
		  	    		else
		  	    			if (!paths[x].contains(":"))
		  	    				element = (Element)element.getElementsByTagName(domainPrefix + paths[x]).item(0);
		  	    			else
		  	    				element = (Element)element.getElementsByTagName(paths[x]).item(0);
	    	    }
	    	    return (element==null || element.getAttribute(attrib)==null || element.getAttribute(attrib).isEmpty()) ? "" : element.getAttribute(attrib);
	    	  }

	      /**
	       * Devuelve el valor del elemento indicado por path
	       * 
	       * @Param path: Path al elemento sin contar la raiz, para referirse a la raiz utilizar null
	       */
	      public String getValue(String p){
	  	    Element element = xmlDoc.getDocumentElement();
	  	    if (p!=null){
	  	    	String[] path = p.split("/");
	  	    	for (int x=0;x<path.length;x++)
	  	    		if (element==null)
	  	    			break;
	  	    		else
	  	    			if (!path[x].contains(":") && element.getElementsByTagName(domainPrefix + path[x]).item(0)!=null)
	  	    				element = (Element)element.getElementsByTagName(domainPrefix + path[x]).item(0);
	  	    			else
	  	    				element = (Element)element.getElementsByTagName(path[x]).item(0);
	  	    }
	  	    return (element==null  || element.getTextContent()==null) ? "" : element.getTextContent();
	  	  }
	    
	      /**
	       * Obtiene el valor de la ruta espesificada por s dentro del XML
	       * detecta si es valor o atributo.
	       */
	      @Override
	      public String get(String s){
	    	    if (getValue(s).isEmpty()){
	    		  String path=null,att="";
	    		  String[] fullPath = s.split("/");
	    		  for (int x=0;x<fullPath.length-1;x++){
	    			  path= (path==null ? "" : path)+fullPath[x];
	    			  if (x<fullPath.length-2)
	    				  path=path+"/";
	    		  }
	    		  att = fullPath[fullPath.length-1];
    			  return getAttrib(path,att);
	    	  }else
	    		  return getValue(s);
	      }
	      
	      
	      /**
	       * Devuelve todos los nodos del elemento indicado por path. Lista vacia si no hay elementos y null si algun elemento del path no existe.
	       * 
	       * @Param path: Path al elemento sin contar la raiz, para referirse a la raiz utilizar null
	       */
	    public NodeList getNodes(String p){
	  	    Element element = xmlDoc.getDocumentElement();
	  	    NodeList lst=null;
	  	    if (p!=null){
	  	    	String[] path = p.split("/");
	  	    	for (int x=0;x<path.length;x++)
	  	    		if (x+1==path.length)
	  	    			if (element!=null){
	  	    				lst = element.getElementsByTagName(domainPrefix + path[x]);
	  	    				if (lst.getLength()==0)
	  	    					lst = element.getElementsByTagName(path[x]);
	  	    			}else
	  	    				lst = new NodeList(){

								@Override
								public int getLength() {
									// TODO Auto-generated method stub
									return 0;
								}

								@Override
								public Node item(int index) {
									// TODO Auto-generated method stub
									return null;
								}};
	  	    		else
	  	    			if (element==null)
	  	    				return null;
	  	    			else{
	  	    				if ((Element)element.getElementsByTagName(domainPrefix + path[x]).item(0)==null)
	  	    					element = (Element)element.getElementsByTagName(path[x]).item(0);
	  	    				else
	  	    					element = (Element)element.getElementsByTagName(domainPrefix + path[x]).item(0);
	  	    			}
	  	    }
	  	    return lst;
	  	  }
	      
	      
	    /**
	     * Devuelve el objecto como una cadena de texto del documento XML
	     * 
	     * 
	     * @see java.lang.Object#toString()
	     */
	      @Override
		public String toString(){
	    	  return getAsText();
	      }
	      
	 /**
	  * Regresa el nombre del primer elemento
	  */
	      public String getDocElementName(){
	    	  if (xmlDoc!=null)
	    		  return xmlDoc.getDocumentElement().getNodeName();
	    	  else
	    		  return "";
	      }
	      
	      
	 /**
	  * Inserta un nodo
	  */
 	 public void insert(String path, Element data){
		 insertBefore(doc,path,null,data);
	 }
	 public void insertBefore(String path, String before, Element data){
		 insertBefore(doc,path,(before.isEmpty()?null:before),data);
	 }
	 //Funcion recursiva para insertar
	 public void insertBefore(Element lastElement,String path, String before, Element data){
		 String firstElement=null;
		 String elementName=null;
		 String restPath=null;
		 String condition=null;
		 String conditionParam=null;
		 String conditionValue=null;
		 Element newElement=null;
		 
		 //Quedara path restante???
		 if (path.contains("/")){
			 firstElement = path.split("/")[0];
			 restPath=path.substring(firstElement.length()+1);
		 }else
			 firstElement=path;
		 
		 //hay condicion en el elemento buscado?
		 if (firstElement.contains("?")){
			 elementName = firstElement.split("\\?")[0];
			 condition = firstElement.split("\\?")[1];
			 conditionParam = condition.split("=")[0];
			 conditionValue  = condition.split("=")[1];
		 }else
			 elementName=firstElement;
		 
		 
		 //Hay condicion
		 if (condition!=null){
			//Buscamos el Elemento
			 NodeList elementsList = lastElement.getElementsByTagName(elementName);
			 for (int x=0;x<elementsList.getLength();x++){
				 String value = ((Element)elementsList.item(x)).getAttribute(conditionParam);
				 if (((Element)elementsList.item(x)).getAttribute(conditionParam).equals(conditionValue)){
					 newElement = (Element) elementsList.item(x);
					 break;
				 }
			 }
		 }else
			 if (elementName!=null && !elementName.isEmpty())
				 newElement = (Element) lastElement.getElementsByTagName(elementName).item(0);
			 else
				 newElement = lastElement;
		 
		 //Resta path??? Seguimos recursivamente
		 if(restPath!=null){
			 insertBefore(newElement,restPath,before,data);
		 }else{
			//No resta path, debemos insertar en este elemento
				 //before???
				 Node beforeNode=null;
				 if (before!= null && !before.isEmpty()){
					 String beforeNodeName=null;
					 String beforeCondition=null;
					 String beforeConditionAttrib=null;
					 String beforeConditionValue=null;
					 //hay condicion
					 if (before.contains("?")){
						 beforeNodeName=before.split("\\?")[0];
						 beforeCondition=before.split("\\?")[1];
						 beforeConditionAttrib=beforeCondition.split("=")[0];
						 beforeConditionValue=beforeCondition.split("=")[1];
					 }else
						 beforeNodeName=before;
					 
					 NodeList beforList = newElement.getElementsByTagName(beforeNodeName);
					 for (int x=0;x<beforList.getLength();x++)
						 if (beforList.item(x).getAttributes().getNamedItem(beforeConditionAttrib).getNodeValue().equals(beforeConditionValue)){
							 beforeNode=beforList.item(x);
							 break;
						 }
				 }
				  Node newNode = xmlDoc.importNode(data, true);
				  if (beforeNode!=null)
					  newElement.insertBefore(newNode,beforeNode);
				  else
					  newElement.appendChild(newNode);
		 }
	 }

	 /**
	  * Elimina un elemento del documento, identificado por un id
	  * @param id Id del elemento al eliminar.
	  */
	  public void remove(String id){
		  Node n = xmlDoc.getElementById(id);
		  n.getParentNode().removeChild(n);
	  }
	  
	  public void scanForIds(){
		  scanForIds(doc);
	  }
	  
	  private void scanForIds(Node n){
		  //siempre el primer hijo es de tipo texto
		  if (!(n instanceof Element)){
		  	if (n.getNextSibling()!=null)
		  		scanForIds(n.getNextSibling());
		  	return;
		  }
		  //Todos los atributos
		  if (n.getAttributes()!=null)
			  for (int x=0 ; x<n.getAttributes().getLength();x++)
				  if (n.getAttributes().item(x).getNodeName().equals("id")){
					  ((Element)n).setIdAttribute("id", true);
				  }
		  //Todos los hijos
		  if (n.getFirstChild()!=null)
			  scanForIds(n.getFirstChild());
		  
		  //Todos los hermanos
		  if (n.getNextSibling()!=null && n instanceof Element)
			  scanForIds(n.getNextSibling());
	  }
	  
	  
	  
	  /**
	   * Guarda el archivo XML.
	   * 
	   * @param path Ruta para guardar.
	   * @return
	 * @throws IOException 
	   */
	  public boolean saveXML(String path) throws IOException {
		    try {
		      OutputStream fout= new FileOutputStream(path);
		      OutputStream bout= new BufferedOutputStream(fout);
		      OutputStreamWriter out = new OutputStreamWriter(bout, "UTF8");
		      out.write(getAsText());
		      out.flush();
		      out.close();
		    }
		    catch (UnsupportedEncodingException e) {
		      System.out.println("La Máquina Virtual no soporta la codificación UTF8.");
		      return false;
		    }
		    return true;
		  }
}